package core;


import java.util.*;
import persistence.TransactionManager;
import persistence.dao.IContainerToProductsDAO;
import persistence.dao.IItemsDAO;
import persistence.dao.IProductsDAO;
import persistence.dto.ItemDTO;

/**Manages the items in HIT. does not create the items  A singleton class
 * 
 */
public class ItemManager extends ModelObject
{
    /**
	 * used for Serialization
	 */
	private static final long serialVersionUID = 8820918001559965212L;
	/** a list of all the items */
    private Set<Item> allItems;
    /** a list of all the removed items */
    private Set<Item> allRemovedItems;
    /** a mapping of item barcodes to items */
    private Map<Barcode, Item> barcodeToItems;
    /** a mapping of products to items */
    private Map<Product, Set<Item>> prodToItems;
    /** a mapping of product containers to a set of items */
    private Map<ProductContainer, Set<Item>> productContainerToItems;
    /** a single instance of ItemManager*/
    private static ItemManager instance;
    /**
     * persistence pointer to ItemDAO
     */
    private IItemsDAO itemDAO;
    
	private Calendar lastReportDate;

	
	public void addRemovedItem(Item i)
    {
		allRemovedItems.add(i);
    }
	
	public void setLastDate(Calendar c)
	{
		lastReportDate = c;
	}
	
	public Calendar getLastDate()
	{
		return lastReportDate;
	}
	
        /** constructor set to private for Singleton design 
         */
	private ItemManager()
	{
            setMaps();
           
	}
	
	public void Initialize()
	{
		 itemDAO = CoreFacade.getInstance().getFactory().getItemsDAO();
	}
        /* maintains the Singleton design.
         * 
         */
    public static ItemManager getInstance()
    {
        if(instance == null)
        {
            instance = new ItemManager();
        }
        return instance;
    }
    
    public static void destroyInstance()
    {
    	instance = null;
    }
    
    /** A method needed for testing purposes
     * 
     */
    public static void removeInstance()
    {
        instance = null;
    }
    
    /** initialized the ItemManager's maps
     * 
     */
    private void setMaps()
    {
        allItems = new HashSet<Item>();
        allRemovedItems = new HashSet<Item>();
        barcodeToItems = new HashMap<Barcode, Item>();
        prodToItems = new HashMap<Product, Set<Item>>();
        productContainerToItems = new HashMap<ProductContainer, Set<Item>>();
    }
    
    /** Returns all items in container
     * 
     * @param pc Product container in which to look for items
     * @return a set of items, may be empty
     * @throws Exception
     */
    public Set<Item> getItemsFromContainer(ProductContainer pc) throws Exception
    {
    	assert (pc != null);
    	if(pc == null)
    		throw new Exception("ProductContainer pc was null");
    	return (HashSet<Item>)productContainerToItems.get(pc);
    }
    
    /** gets all of the items in a product container
     * 
     * @param pc product container to search
     * @return collection of items found in pc
     * @throws Exception
     */
    public Collection<Item> getAllItemsInPC(ProductContainer pc)throws Exception
    {
        assert (pc != null);
        
        if(pc == null)
        {
            throw new Exception("product container passed in was set to null");
        }
        
        if(!productContainerToItems.containsKey(pc))
        {
            throw new Exception("product container cannot be found.");
        }
        
        Set <Item> bunchOfItems = productContainerToItems.get(pc);
        
        return bunchOfItems;
    }
    
	/**adds an item to the list of all items and to the maps.
	 * @param item item to be added to system
	 * @throws Exception
	 * @return true if successful
	 */
	public boolean addItem(Item item) throws IllegalArgumentException
	{
        assert item != null;
        
        if(!isValidItem(item))
        {
            throw new IllegalArgumentException("not a valid item.");
        }
        
        //adds to list
        allItems.add(item);
        
        //adds to barcodeToItems
        barcodeToItems.put(item.getBarcode(), item);
        
        //adds to prodToItems
        if(prodToItems.containsKey(item.getProduct()))
        {
            Set<Item> setOfItems = prodToItems.get(item.getProduct());
            setOfItems.add(item);
        }
        else
        {
            Set<Item> setOfItems = new HashSet<Item>();
            setOfItems.add(item);
            prodToItems.put(item.getProduct(), setOfItems);
        }
        
        //adds to productContainerToItems
        if(productContainerToItems.containsKey(item.getProductContainer()))
        {
            Set<Item> setOfItems = 
                    productContainerToItems.get(item.getProductContainer());
            setOfItems.add(item);
        }
        else
        {
        	//Code should never be run.
            Set<Item> setOfItems = new HashSet<Item>();
            setOfItems.add(item);
            productContainerToItems.put(item.getProductContainer(), setOfItems);
        }
        
       
        
        
        return true;
	}
	
	/**
	 * Add ProductContainer to the necessary map
	 * @param pc to add
	 * @return
	 * @throws Exception 
	 */
	public void addProductContainer(ProductContainer pc) throws Exception
	{
		assert(pc != null);
		if (pc == null)
			throw new Exception("null pc");
		if(!productContainerToItems.containsKey(pc))
			productContainerToItems.put(pc, new HashSet<Item>());
                
                
	}
    /**modifies the product container attribute and updates maps
     * 
     * @param i the item that needs modifying
     * @param pc the new product container the item is in
     * @throws Exception
     * @return true on success
     */
    public boolean addItemToContainer
            (Item i, ProductContainer newPc) throws Exception
    {
        assert i != null;
        assert newPc != null;
        assert(isValidItem(i));
        
        if(!isValidItem(i))
        {
            throw new Exception("invalid item.");
        }
        if(newPc == null)
        {
            throw new Exception("invalid product container.");
        }
        
        ProductContainer old = i.getProductContainer();
        TransactionManager.begin();
        i.setProductContainer(newPc);
		IItemsDAO dao = CoreFacade.getInstance().getFactory().getItemsDAO();
		boolean commit = true;
		commit = dao.edit(i.createDTO());
		// set it back for regular behavior	
        i.setProductContainer(old);
		TransactionManager.end(commit);
		if(!commit)
			return false;
        if(productContainerToItems.containsKey(i.getProductContainer()))
        {
            //removes from old product container
            Set<Item> oldSetOfItems = productContainerToItems.get(i.getProductContainer());
            oldSetOfItems.remove(i);
        }
        else
        {
            throw new Exception("item not found in old product container.");
        }
        
        
        
        //updates item attribute
        i.setProductContainer(newPc);
        
        addItem(i);
        /*
        //adds to new product container
        if(productContainerToItems.containsKey(i.getProductContainer()))
        {
            Set<Item> setOfPCs = 
                    productContainerToItems.get(i.getProductContainer());
            setOfPCs.add(i);
        }
        else
        {
            Set<Item> setOfPCs = new HashSet();
            setOfPCs.add(i);
            productContainerToItems.put(i.getProductContainer(), setOfPCs);
        }*/
        
        
        return true;
    }
        
    /**checks in item is valid 
     * @param i the item to check if valid
     * @returns true if valid
     */
    private boolean isValidItem(Item i)
    {
        assert i != null;
        
        if(i.getBarcodeString().isEmpty()) return false;
        if(i.getProduct() == null) return false;
        if(i.getProductContainer() == null) return false;

        return true;
    }
    /**removes an item from the maps but not the list of all items
     * 
     * @param item the item to remove
     * @return true if successfully removed
     * @throws IllegalArgumentException
     * @throws Exception 
     */
	public boolean removeItem
                (Item item)throws IllegalArgumentException, Exception
	{
        // Don't remove from list, only remove from Maps
        
        assert item !=  null;
        
        if(item == null)
        {
            throw new Exception("item was set to null.");
        }
        
        if(!allItems.contains(item))
        {
            throw new IllegalArgumentException("Item to remove was not found.");
        }
        
        if(item.isRemoved())
        {
            throw new Exception("item has already been removed.");
        }
        
        //remove from map
        barcodeToItems.remove(item.getBarcode());
        
        //remove from map
        Set<Item> bunchOfItems = prodToItems.get(item.getProduct());
        bunchOfItems.remove(item);
        
        //remove from map
        Set<Item> i = productContainerToItems.get(item.getProductContainer());
        i.remove(item);
        
        //Set expiration date and remove parent
        item.removeItem();
        
        allItems.remove(item);
        allRemovedItems.add(item);
        
        //Persistence
        TransactionManager.begin();
        IItemsDAO dao = CoreFacade.getInstance().getFactory().getItemsDAO();
        ItemDTO dto = item.createDTO();
        boolean commit = dao.edit(dto);
        if(commit)
        {
            TransactionManager.end(commit);
            return true;
        }
        else{
            TransactionManager.end(!commit);
            return false;
        }
        
        
        
        
	}
    /**checks if an items needs to be removed
     * is this method even necessary(?)
     * 
     * @param item item to be removed
     * @return true if item can be removed
     */
	public boolean canRemoveItem(Item item)
	{
            assert item != null;
            
            return isValidItem(item);
	}
        
    /**checks if the given Product Container is empty so it can be deleted.
     * 
     * @param pc the product container to see if it's empty
     * @return true if product container is empty
     * @throws Exception 
     */
    public boolean canRemoveContainer(ProductContainer pc)throws Exception
	{
        assert pc != null;
        
        //check is that pc is empty of items.
        if(pc == null)
        {
            throw new Exception("product container was set to null");
        }
        
        Set<Item> setOfItems = productContainerToItems.get(pc);
        if (setOfItems.isEmpty())
        {
            return true;
        }
        
        return false;
	}
    /**removed an item using the barcode
     * 
     * @param bc of the item to remove
     * @return true if successful
     * @throws Exception
     */
    public Item removeItem(Barcode bc)throws Exception
    {
        assert bc != null;
        
        if(bc.getBarcode() == null)
        {
            throw new Exception("barcode was not set.");
        }
        if(bc.getBarcode().isEmpty())
        {
            throw new Exception("barcode is empty.");
        }
        if(bc.getBarcode().length() != BAR_CODE_LENGTH)
        {
            throw new Exception("Barcode is not the right length.");
        }
        
        Item i = barcodeToItems.get(bc);
        removeItem(i);
        
        return i;
    }
        
    /**returns a set of the items
     * 
     * @return set of all the not removed items
     */
    public Collection<Item> getAllItems()
    {
        return allItems;
    }
        
        
    /**returns a set of the items that have been removed
     * 
     * @return set of the removed items
     */
    public Collection<Item> getRemovedItems()
    {
        return allRemovedItems;
    }
    
        
        
    /**checks if the given product is empty so it can be deleted
     * 
     * @param p the product to check if empty
     * @return true if empty
     * @throws Exception 
     */
    public boolean canRemoveProduct(Product p)throws Exception
	{
        assert p != null;
        
        //check is that product set is empty of items.
        if(p == null)
        {
            throw new Exception("the product passed in was set to null.");
        }
        
        if(prodToItems.isEmpty())
        {
            return true;
        }
        
        if(!prodToItems.containsKey(p))
        {
            throw new Exception("the product passed was not stored in "
                    + "'Products to Items' collection.");
        }
        
        Set<Item> setOfItems = prodToItems.get(p);
        
        if (setOfItems.isEmpty())
        {
            return true;
        }
        
        return false;
	}
        
    /**modifies an existing item.  Updates the set of all items and the maps
     * 
     * @param before the old item to be replaced
     * @param after the new item to replace the old item
     * @return true if successful
     * @throws IllegalArgumentException
     * @throws Exception 
     */
	public boolean editItem(Item before, Item after) 
            throws IllegalArgumentException, Exception
	{
        //persistence
        TransactionManager.begin();
        
        ItemDTO iDTO = after.createDTO();
        
        boolean success = itemDAO.edit(iDTO);
        TransactionManager.end(success);
        if(!success)//if false, don't run the rest of the method
        {
            System.out.println("failed to modify DB");
            return false; 
        } 
            
        assert before != null;
        assert after != null;
        
        if(after == null)
        {
            throw new Exception("updated item was set to null");
        }
        if(!isValidItem(after))
        {
            throw new IllegalArgumentException("not a valid item.");
        }   
        
        //updates maps
        removeItem(before);
        //update all item
        allItems.remove(before);
        addItem(after);
        
        
        return true;
	}
  
	/** look for an item by its barcode
	 * 
	 * @param barcode item's barcode
	 * @return the item if found
	 * @throws Exception
	 */
	public Item getItem(Barcode barcode) throws Exception
	{
            assert barcode != null;
            
            if(barcode == null)
            {
                throw new Exception("barcode was null");
            }
            
            Item item = null;
            if(barcodeToItems.containsKey(barcode))
            {
                item = barcodeToItems.get(barcode);
            }
            return item;
	}
        /**returns an item based on its barcode
         * 
         * @param bCode the barcode of a valid item
         * @return the item
         */
        public Item getItem(String bCode)
        {
            return barcodeToItems.get(new ItemBarcode(bCode));
        }
	
	/**
	 * The returns all of the items associated with the Product passed in. If the 
	 * product does not exist this method returns null.
	 * 
	 * @param p The product to get the items for.
	 * @return Returns a set of Items or null if the product does not exist.
	 */
	public Set<Item> getItems(Product p)
	{
		assert p != null;
		Set<Item> items = prodToItems.get(p);
		if(items == null)
			return new HashSet<Item>();
		return items;
	}
        
        
    
	public void setLastBarcode() 
	{
		List<Item> items = new ArrayList<Item>(allItems);
		List<Item> removedItems = new ArrayList<Item>(allRemovedItems);
		long high = 0;
		for(Item item : items)
		{
			long cur = Long.parseLong(item.getBarcodeString().substring(0,
											item.getBarcodeString().length() -1));
			if(high < cur)
			{
				high = cur;
			}
		}
		for(Item item : removedItems)
		{
			long cur = Long.parseLong(item.getBarcodeString().substring(0,
					item.getBarcodeString().length() -1));
			if(high < cur)
			{
				high = cur;
			}
		}
		if(high >0)
		{
			high++;
			prevBarcode = Long.toString(high);
		}
		
	}
	
      //Barcode Stuff
    	public static int BAR_CODE_LENGTH = 12;
        public static String BCODE_TWO_NUMS = "42";
        private static long nextBarCode = 42200000000L;
        private String prevBarcode;
        
        public ItemBarcode generateBarcode()
    	{   
        	/*if(prevBarcode != null)
        	{
        		nextBarCode = Long.parseLong(prevBarcode) + 1;
        	}
        	prevBarcode = Long.toString(nextBarCode);
            String nextBCode = BCODE_TWO_NUMS + nextBarCode;
            
            ItemBarcode bCode = new ItemBarcode(nextBCode);
                
    		return bCode;*/
        	if(prevBarcode != null)
        	{
        		nextBarCode = Long.parseLong(prevBarcode);
        	}
        	prevBarcode = Long.toString(nextBarCode);
            String nextBCode = "" + nextBarCode;
            
            //generate Checksum
            //append to nextBCode
            nextBCode += generateChecksum(nextBCode);
            
            ItemBarcode bCode = new ItemBarcode(nextBCode);
                
    		return bCode;
    	}
        
        public int generateChecksum(String barcode)
        {
        	String first = "" + barcode.charAt(0);
        	String second = "" + barcode.charAt(1);
        	String third = "" + barcode.charAt(2);
        	String fourth = "" + barcode.charAt(3);
        	String fifth = "" + barcode.charAt(4);
        	String sixth = "" + barcode.charAt(5);
        	String seventh = "" + barcode.charAt(6);
        	String eighth = "" + barcode.charAt(7);
        	String ninth = "" + barcode.charAt(8);
        	String tenth = "" + barcode.charAt(9);
        	String eleventh = "" + barcode.charAt(10);
        	
        	int addOdd = Integer.parseInt(first) + Integer.parseInt(third) +
        			Integer.parseInt(fifth) + Integer.parseInt(seventh) +
        			Integer.parseInt(ninth) + Integer.parseInt(eleventh);
        	
        	addOdd *= 3;
        	
        	//add the even
        	addOdd += Integer.parseInt(second) + Integer.parseInt(fourth) + 
        			Integer.parseInt(sixth) + Integer.parseInt(eighth) + Integer.parseInt(tenth);
        	
        	int result = addOdd % 10;
        	
        	if (result == 0) 
        		return 0;
        	else
        		return 10-result;
        }
        
        public void decrementBarcodeGenerator()
        {
        	long previous = Long.parseLong(prevBarcode);
        	prevBarcode = Long.toString(previous - 1);
        }
        
        public void incrementBarcodeGenerator()
        {
        	long previous = Long.parseLong(prevBarcode);
        	prevBarcode = Long.toString(previous + 1);
        }
        
        public void undoAddItem(Item item, boolean productAdded)
        {
        	assert item != null;
        	allItems.remove(item);
        	barcodeToItems.remove(item.getBarcode());
        	productContainerToItems.get(item.getProductContainer()).remove(item);

        	if(!productAdded)
        	{//Product was already in system
        		prodToItems.get(item.getProduct()).remove(item);
        	}
        	else
        	{//Product was added during the last command
        		prodToItems.remove(item.getProduct());
        	}
        }
        
        public void undoRemoveItem(Item item, ProductContainer parent) throws Exception
        {           
            if(!item.isRemoved())
            {
                throw new Exception("Item is not removed, you cannot undo this");
            }         
//            //remove from map
//            barcodeToItems.remove(item.getBarcode());
            barcodeToItems.put(item.getBarcode(), item);
//            
//            //remove from map
            Set<Item> bunchOfItems = prodToItems.get(item.getProduct());
            bunchOfItems.add(item);
//            
//            //remove from map
            Set<Item> i = productContainerToItems.get(parent);
            i.add(item);
//            
//            //Set expiration date and remove parent
            item.undoRemoveItem(parent);
//            
            allItems.add(item);
            allRemovedItems.remove(item);
            
          //Persistence
            TransactionManager.begin();
            IItemsDAO dao = CoreFacade.getInstance().getFactory().getItemsDAO();
            ItemDTO dto = item.createDTO();
            boolean commit = dao.edit(dto);
            
            TransactionManager.end(commit);
            if(!commit)
            {
            	return;
            }
        }
        
        public void updateExpirationDates(Product p)
        {
            	Set<Item> items = this.getItems(p);
        	
        	Iterator<Item> iter = items.iterator();
        	while(iter.hasNext())
        	{
        		Item cur = iter.next();
        		cur.setExpirationDate(); //persistence with in this method
        	}
        }
        
        public boolean isItemExpired(Item i)
        {
            Calendar now = new GregorianCalendar();
            
            if(i.getProduct().getShelfLife() == 0)
            {
                return false;
            }
            
            return i.getExpirationDate().before(now);
        }

}
